package ro.flashbite.utils.framer
{
	import flash.events.TimerEvent;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	import ro.flashbite.errors.SingletonError;
	import ro.flashbite.interfaces.IDestroyable;
	import ro.flashbite.loggers.ILogger;
	import ro.flashbite.loggers.LoggerFactory;
	
	/**
	 * Base class for time management
	 * 
	 * v 1.1
	 * 
	 * @author Adrian Barbu
	 */	
	public final class FrameBeacon implements IDestroyable, ITickUpdater
	{
		/**
		 * states that the beacon can have 
		 */		
		private static const STATE_STOPPED		:	uint = 0;
		private static const STATE_PLAYING		:	uint = 1;
		private static const STATE_PAUSED		:	uint = 2;
		/**
		 * singleton instance 
		 */		
		private static var _instance	:	FrameBeacon;
		/**
		 * each class can register here functions to be called as a real ENTER_FRAME event 
		 */		
		private var _callbacks			:	Vector.<Function>;
		/**
		 * holds the length of the callbacks; this is invariable and can be modified on the fly
		 */		
		private var _callbacksLength	:	int;
		/**
		 * holds the used stage fps value 
		 */		
		private var _fps				:	uint;
		/**
		 * current active state 
		 */		
		private var _activeState		:	uint;
		/**
		 * holds the last getTimer() on last enter frame event 
		 */		
		private var _lastTime			:	uint;
		/**
		 * logger for debug purpose
		 */		
		private var _logger				:	ILogger;
		/**
		 * timer used to simulate enterFrame events
		 */		
		private var _timer				:	Timer;
		/**
		 * marker for application type -> ENTER_FRAME or Timer based
		 */		
		private var _appIsTimeBased		:	Boolean;
		
		// ================================================================================================================================
		// CONSTRUCTOR
		// ================================================================================================================================
		
		public function FrameBeacon(enforcer:SingletonEnforcer, fps:uint, appIsTimeBased:Boolean)
		{
			if (enforcer == null)
				throw new SingletonError();
			
			_fps = fps;
			_appIsTimeBased = appIsTimeBased;
			
			_callbacks = new Vector.<Function>();
			_callbacksLength = 0;
			
			_activeState = STATE_STOPPED;
			
			_logger = LoggerFactory.getLogger("FrameBeacon");
		}
		
		public static function getInstance(fps:uint = 30, appIsTimeBased:Boolean = false):ITickUpdater
		{
			if (_instance == null) _instance = new FrameBeacon(new SingletonEnforcer(), fps, appIsTimeBased);
			
			return _instance;
		}
		
		// ================================================================================================================================
		// DESTRUCTOR
		// ================================================================================================================================
		
		public function destroy():void
		{
			stop();
			
			_callbacks = null;
			if (_logger) {
				LoggerFactory.clearLogger(_logger.name);
				_logger = null;
			}
			
			if (_timer) {
				setListeners(false);
				_timer = null;
			}
			
			_instance = null;
		}
		
		// ================================================================================================================================
		// PUBLIC
		// ================================================================================================================================
		
		// --------------------------------------------------------------------------------------------------------------------------------
		// start, pause, unpause, stop management
		public function start():void
		{
			if (_activeState != STATE_STOPPED) {
				_logger.debug("Current state isn't stopped. Couldn't start");
				return;
			}
			
			_activeState = STATE_PLAYING;
			
			_timer = new Timer(1000 / _fps);
			
			setListeners(true);
			_lastTime = getTimer();
		}
		
		public function pause():void
		{
			_activeState = STATE_PAUSED;
			setListeners(false);
		}
		
		public function unpause():void
		{
			_activeState = STATE_PLAYING;
			setListeners(true);
			_lastTime = getTimer();
		}
		
		public function stop():void
		{
			pause();
		}
		// --------------------------------------------------------------------------------------------------------------------------------
		
		// --------------------------------------------------------------------------------------------------------------------------------
		// callback's registration
		public function registerCallback(callback:Function):void
		{
			if (_callbacks.indexOf(callback) == -1) {
				_callbacks.push(callback);
			}
			_callbacksLength = _callbacks.length;
		}
		
		public function unregisterCallback(callback:Function):void
		{
			if (_callbacks.indexOf(callback) > -1) {
				_callbacks.splice(_callbacks.indexOf(callback), 1);
			}
			_callbacksLength = _callbacks.length;
		}
		// --------------------------------------------------------------------------------------------------------------------------------
		
		// ================================================================================================================================
		// PRIVATE
		// ================================================================================================================================
		
		/**
		 * Start, stop on enter frame beacon
		 */		
		private function setListeners(startBeacon:Boolean = true):void
		{
			if (_timer) {
				if (startBeacon) {
					_timer.addEventListener(TimerEvent.TIMER, onTick, false, 0, true);
					_timer.start();
				} else {
					_timer.stop();
					_timer.removeEventListener(TimerEvent.TIMER, onTick);
				}
			}
		}
		
		/**
		 * On this function will be calculated time passed till last update and the registered callback functions will be called
		 */		
		private function onTick(e:TimerEvent):void
		{
			var currentTime:uint = getTimer(),
				timePerFrame:uint = currentTime - _lastTime,
				i:int,
				param:Number = timePerFrame / 1000; //save time in percent .012 sec instead of 12 ms
			
			//save the timer
			_lastTime = currentTime;
			
			//trace("Time per frame : " + timePerFrame);
			//trace("Timer delay : " + _timer.delay);
			
			for (i = 0; i < _callbacksLength; i++) { //must use a dynamic length of vector because functions can be removed from _callbacks when calling them
				_callbacks[i](param); // ~25% faster than callback.apply(null, [param]);
			}
			
			//mark for a stage update if app is time based
			if (_appIsTimeBased) {
				e.updateAfterEvent();
			}
		}
	}
}

class SingletonEnforcer {}